/*-------------------------------------------------------------------------
 *
 * tidpath.c
 *	  确定哪些TID条件可用于扫描给定关系，并相应地创建TidPaths和TidRangePaths的例程。
 *
 * 对于TidPaths，我们查找形式为"CTID = pseudoconstant"的WHERE条件，
 * 这可以通过直接获取元组来实现，方法是通过heap_fetch()。
 * 我们还可以处理诸如(CTID = const1) OR (CTID = const2)的OR条件，以及
 * 形如CTID = ANY(pseudoconstant_array)的ScalarArrayOpExpr条件。
 * 特别是，这允许
 *		WHERE ctid IN (tid1, tid2, ...)
 *
 * 和索引扫描一样，我们的"伪常量"定义相当宽松：
 * 我们允许不涉及可变函数或所考虑关系的Var的任何内容。
 * 属于查询其他关系的Vars是被允许的，从而产生参数化的TID扫描。
 *
 * 我们还支持"WHERE CURRENT OF cursor"条件（CurrentOfExpr），
 * 这相当于"CTID = 运行时确定的TID"。
 * 理论上，这些可以转换为CTID与函数结果的简单比较，
 * 但在实践中，保持特殊节点表示直至执行更为有效。
 *
 * 此外，可以为"CTID relop pseudoconstant"这类条件创建TidRangePaths，
 * 其中relop是 >, >=, <, <=之一，以及由这些条件组成的AND子句。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/path/tidpath.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/sysattr.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/restrictinfo.h"


/*
 * 这个 Var 是否表示指定基关系的 CTID 列？
 */
static inline bool fc_IsCTIDVar(Var *fc_var, RelOptInfo *fc_rel)
{
	/* vartype 检查纯粹是偏执 */
	if (fc_var->varattno == SelfItemPointerAttributeNumber &&
		fc_var->vartype == TIDOID &&
		fc_var->varno == fc_rel->relid &&
		fc_var->varlevelsup == 0)
		return true;
	return false;
}

/*
 * 检查 RestrictInfo 是否为以下形式
 *		CTID OP 伪常量
 * 或
 *		伪常量 OP CTID
 * 其中 OP 是一个二元操作，CTID Var 属于关系“rel”，而子句的另一侧没有。
 */
static bool fc_IsBinaryTidClause(RestrictInfo *fc_rinfo, RelOptInfo *fc_rel)
{
	OpExpr	   *fc_node;
	Node	   *fc_arg1,
			   *fc_arg2,
			   *fc_other;
	Relids		fc_other_relids;

	/* 必须是 OpExpr */
	if (!is_opclause(fc_rinfo->clause))
		return false;
	fc_node = (OpExpr *) fc_rinfo->clause;

	/* OpExpr 必须有两个参数 */
	if (list_length(fc_node->args) != 2)
		return false;
	fc_arg1 = linitial(fc_node->args);
	fc_arg2 = lsecond(fc_node->args);

	/* 查找 CTID 作为任一参数 */
	fc_other = NULL;
	fc_other_relids = NULL;
	if (fc_arg1 && IsA(fc_arg1, Var) &&
		fc_IsCTIDVar((Var *) fc_arg1, fc_rel))
	{
		fc_other = fc_arg2;
		fc_other_relids = fc_rinfo->right_relids;
	}
	if (!fc_other && fc_arg2 && IsA(fc_arg2, Var) &&
		fc_IsCTIDVar((Var *) fc_arg2, fc_rel))
	{
		fc_other = fc_arg1;
		fc_other_relids = fc_rinfo->left_relids;
	}
	if (!fc_other)
		return false;

	/* 另一个参数必须是伪常量 */
	if (bms_is_member(fc_rel->relid, fc_other_relids) ||
		contain_volatile_functions(fc_other))
		return false;

	return true;				/* 成功 */
}

/*
 * 检查 RestrictInfo 是否为以下形式
 *		CTID = 伪常量
 * 或
 *		伪常量 = CTID
 * 其中 CTID Var 属于关系“rel”，而子句的另一侧没有。
 */
static bool fc_IsTidEqualClause(RestrictInfo *fc_rinfo, RelOptInfo *fc_rel)
{
	if (!fc_IsBinaryTidClause(fc_rinfo, fc_rel))
		return false;

	if (((OpExpr *) fc_rinfo->clause)->opno == TIDEqualOperator)
		return true;

	return false;
}

/*
 * 检查 RestrictInfo 是否为以下形式
 *		CTID OP 伪常量
 * 或
 *		伪常量 OP CTID
 * 其中 OP 是一个范围操作符，例如 <, <=, > 或 >=，CTID Var 属于关系“rel”，而子句的另一侧没有。
 */
static bool fc_IsTidRangeClause(RestrictInfo *fc_rinfo, RelOptInfo *fc_rel)
{
	Oid			fc_opno;

	if (!fc_IsBinaryTidClause(fc_rinfo, fc_rel))
		return false;
	fc_opno = ((OpExpr *) fc_rinfo->clause)->opno;

	if (fc_opno == TIDLessOperator || fc_opno == TIDLessEqOperator ||
		fc_opno == TIDGreaterOperator || fc_opno == TIDGreaterEqOperator)
		return true;

	return false;
}

/*
 * 检查 RestrictInfo 是否为以下形式
 *		CTID = ANY (伪常量数组)
 * 其中 CTID Var 属于关系“rel”，而子句的另一侧没有。
 */
static bool fc_IsTidEqualAnyClause(PlannerInfo *fc_root, RestrictInfo *fc_rinfo, RelOptInfo *fc_rel)
{
	ScalarArrayOpExpr *fc_node;
	Node	   *fc_arg1,
			   *fc_arg2;

	/* 必须是 ScalarArrayOpExpr */
	if (!(fc_rinfo->clause && IsA(fc_rinfo->clause, ScalarArrayOpExpr)))
		return false;
	fc_node = (ScalarArrayOpExpr *) fc_rinfo->clause;

	/* 操作符必须是 tideq */
	if (fc_node->opno != TIDEqualOperator)
		return false;
	if (!fc_node->useOr)
		return false;
	Assert(list_length(fc_node->args) == 2);
	fc_arg1 = linitial(fc_node->args);
	fc_arg2 = lsecond(fc_node->args);

	/* CTID 必须是第一个参数 */
	if (fc_arg1 && IsA(fc_arg1, Var) &&
		fc_IsCTIDVar((Var *) fc_arg1, fc_rel))
	{
		/* 另一个参数必须是伪常量 */
		if (bms_is_member(fc_rel->relid, pull_varnos(fc_root, fc_arg2)) ||
			contain_volatile_functions(fc_arg2))
			return false;

		return true;			/* 成功 */
	}

	return false;
}

/*
 * 检查 RestrictInfo 是否是引用“rel”的 CurrentOfExpr。
 */
static bool fc_IsCurrentOfClause(RestrictInfo *fc_rinfo, RelOptInfo *fc_rel)
{
	CurrentOfExpr *fc_node;

	/* 必须是 CurrentOfExpr */
	if (!(fc_rinfo->clause && IsA(fc_rinfo->clause, CurrentOfExpr)))
		return false;
	fc_node = (CurrentOfExpr *) fc_rinfo->clause;

	/* 如果它引用了这个关系，那么我们就可以了 */
	if (fc_node->cvarno == fc_rel->relid)
		return true;

	return false;
}

/*
 * 从给定的 RestrictInfo 提取一组 CTID 条件
 *
 * 返回指定 rel 的 CTID 质疑 RestrictInfos 列表（在列表中隐含地具有 OR 语义），
 * 如果没有可用的条件则返回 NIL。
 *
 * 该函数仅考虑基本情况；AND/OR 组合将在下面处理。 因此返回的列表永远不会有超过一个元素。
 * （使用列表可能看起来有点奇怪，但它简化了调用者的处理。）
 */
static List * fc_TidQualFromRestrictInfo(PlannerInfo *fc_root, RestrictInfo *fc_rinfo, RelOptInfo *fc_rel)
{
	/*
	 * 我们可以忽略伪常量子句（它们无法包含 Vars，因此无论如何都无法匹配）。
	 */
	if (fc_rinfo->pseudoconstant)
		return NIL;

	/*
	 * 如果子句必须在某些低安全级别限制子句之后等待，则拒绝它。
	 */
	if (!restriction_is_securely_promotable(fc_rinfo, fc_rel))
		return NIL;

	/*
	 * 检查所有基本情况。如果找到匹配项，则返回该子句。
	 */
	if (fc_IsTidEqualClause(fc_rinfo, fc_rel) ||
		fc_IsTidEqualAnyClause(fc_root, fc_rinfo, fc_rel) ||
		fc_IsCurrentOfClause(fc_rinfo, fc_rel))
		return list_make1(fc_rinfo);

	return NIL;
}

/*
 * 从隐式 AND RestrictInfos 列表中提取一组 CTID 条件
 *
 * 返回指定 rel 的 CTID 质疑 RestrictInfos 列表（在列表中隐含地具有 OR 语义），
 * 如果没有可用的相等条件则返回 NIL。
 *
 * 该函数只关心处理 AND/OR 递归。
 */
static List * fc_TidQualFromRestrictInfoList(PlannerInfo *fc_root, List *fc_rlist, RelOptInfo *fc_rel)
{
	List	   *fc_rlst = NIL;
	ListCell   *fc_l;

	foreach(fc_l, fc_rlist)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		if (restriction_is_or_clause(fc_rinfo))
		{
			ListCell   *fc_j;

			/*
			 * 我们必须能够从 OR 的每个子句中提取 CTID 条件，否则我们无法使用它。
			 */
			foreach(fc_j, ((BoolExpr *) fc_rinfo->orclause)->args)
			{
				Node	   *fc_orarg = (Node *) lfirst(fc_j);
				List	   *fc_sublist;

				/* OR 参数应该是 AND 或子 RestrictInfos */
				if (is_andclause(fc_orarg))
				{
					List	   *fc_andargs = ((BoolExpr *) fc_orarg)->args;

					/* 递归以防有子 OR */
					fc_sublist = fc_TidQualFromRestrictInfoList(fc_root, fc_andargs, fc_rel);
				}
				else
				{
					RestrictInfo *fc_rinfo = castNode(RestrictInfo, fc_orarg);

					Assert(!restriction_is_or_clause(fc_rinfo));
					fc_sublist = fc_TidQualFromRestrictInfo(fc_root, fc_rinfo, fc_rel);
				}

				/*
				 * 如果这个分支没有找到任何内容，我们无法对这个 OR 子句做任何事情。
				 */
				if (fc_sublist == NIL)
				{
					fc_rlst = NIL; /* 忘记我们所拥有的任何内容 */
					break;		/* 在 OR 参数的循环之外 */
				}

				/*
				 * 好吧，继续构建隐式 OR 结果列表。
				 */
				fc_rlst = list_concat(fc_rlst, fc_sublist);
			}
		}
		else
		{
			/* 不是 OR 子句，因此处理基本情况 */
			fc_rlst = fc_TidQualFromRestrictInfo(fc_root, fc_rinfo, fc_rel);
		}

		/*
		 * 一旦找到任何可用的 CTID 条件就停止。在理论上，我们
		 * 可以从不同的 AND 子句中获得 CTID 相等条件，
		 * 在这种情况下，我们可以尝试选择最有效的一个。
		 * 实际上，这种用法似乎非常不太可能，所以我们不去考虑；
		 * 一旦找到第一个候选者我们就退出。
		 */
		if (fc_rlst)
			break;
	}

	return fc_rlst;
}

/*
 * 从隐式 AND RestrictInfos 列表中提取一组 CTID 范围条件
 *
 * 返回指定 rel 的 CTID 范围质疑 RestrictInfos 列表
 * （在列表中隐含地具有 AND 语义），如果没有可用的范围条件，
 * 或者如果该 rel 的表 AM 不支持 TID 范围扫描，则返回 NIL。
 */
static List * fc_TidRangeQualFromRestrictInfoList(List *fc_rlist, RelOptInfo *fc_rel)
{
	List	   *fc_rlst = NIL;
	ListCell   *fc_l;

	if ((fc_rel->amflags & AMFLAG_HAS_TID_RANGE) == 0)
		return NIL;

	foreach(fc_l, fc_rlist)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		if (fc_IsTidRangeClause(fc_rinfo, fc_rel))
			fc_rlst = lappend(fc_rlst, fc_rinfo);
	}

	return fc_rlst;
}

/*
 * 给定一个涉及我们 rel 的连接子句的列表，为每个合适的 TidEqual 子句创建一个参数化的 TidPath。
 *
 * 原则上，我们可以组合引用相同外部 rel 的子句，
 * 但看起来这样的情况不会经常出现，值得去关注。
 */
static void fc_BuildParameterizedTidPaths(PlannerInfo *fc_root, RelOptInfo *fc_rel, List *fc_clauses)
{
	ListCell   *fc_l;

	foreach(fc_l, fc_clauses)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);
		List	   *fc_tidquals;
		Relids		fc_required_outer;

		/*
		 * 验证每个子句是否可用；即使是在检查从 EquivalenceClass 生成的子句时，
		 * 我们也必须检查这一点，因为它们可能不满足在另一侧没有我们 rel 的 Vars 的限制，
		 * 或者有人可能构建了接受 "tid" 类型但其中包含其他运算符的运算符类。
		 *
		 * 我们目前只考虑 TidEqual 连接子句。原则上，我们可能会在该 rel 的 joininfo 列表中找到合适的 ScalarArrayOpExpr，
		 * 但似乎没有必要耗费时间去检查。
		 * 而且我们这里绝对不会找到 CurrentOfExpr。因此，我们不
		 * 使用 TidQualFromRestrictInfo；但这必须与该函数匹配。
		 */
		if (fc_rinfo->pseudoconstant ||
			!restriction_is_securely_promotable(fc_rinfo, fc_rel) ||
			!fc_IsTidEqualClause(fc_rinfo, fc_rel))
			continue;

		/*
		 * 检查子句是否可以移动到此 rel；在考虑 EC 派生子句时，这可能是多余的，
		 * 但我们必须为"松散"连接子句检查。
		 */
		if (!join_clause_is_movable_to(fc_rinfo, fc_rel))
			continue;

		/* 好吧，为这个路径创建子句列表 */
		fc_tidquals = list_make1(fc_rinfo);

		/* 计算该路径所需的外部 rel */
		fc_required_outer = bms_union(fc_rinfo->required_relids, fc_rel->lateral_relids);
		fc_required_outer = bms_del_member(fc_required_outer, fc_rel->relid);

		add_path(fc_rel, (Path *) create_tidscan_path(fc_root, fc_rel, fc_tidquals,
												   fc_required_outer));
	}
}

/*
 * 测试一个 EquivalenceClass 成员是否匹配我们 rel 的 CTID Var。
 *
 * 这是 generate_implied_equalities_for_column 使用的回调函数。
 */
static bool fc_ec_member_matches_ctid(PlannerInfo *fc_root, RelOptInfo *fc_rel,
					   EquivalenceClass *fc_ec, EquivalenceMember *fc_em,
					   void *fc_arg)
{
	if (fc_em->em_expr && IsA(fc_em->em_expr, Var) &&
		fc_IsCTIDVar((Var *) fc_em->em_expr, fc_rel))
		return true;
	return false;
}

/*
 * create_tidscan_paths
 *	  创建对应于给定 rel 的直接 TID 扫描的路径。
 *
 *	  候选路径会添加到该 rel 的路径列表中（使用 add_path）。
 */
void create_tidscan_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel)
{
	List	   *fc_tidquals;
	List	   *fc_tidrangequals;

	/*
	 * 如果 rel 的 baserestrict 列表中存在任何合适的质疑，则生成一个
	 * 普通（非参数化）TidPath。
	 */
	fc_tidquals = fc_TidQualFromRestrictInfoList(fc_root, fc_rel->baserestrictinfo, fc_rel);

	if (fc_tidquals != NIL)
	{
		/*
		 * 该路径不使用连接子句，但由于其 tlist 中的 LATERAL 引用，
		 * 可能仍然需要参数化。
		 */
		Relids		fc_required_outer = fc_rel->lateral_relids;

		add_path(fc_rel, (Path *) create_tidscan_path(fc_root, fc_rel, fc_tidquals,
												   fc_required_outer));
	}

	/*
	 * 如果 baserestrict 列表中有范围质疑，则生成一个
	 * TidRangePath。
	 */
	fc_tidrangequals = fc_TidRangeQualFromRestrictInfoList(fc_rel->baserestrictinfo,
													 fc_rel);

	if (fc_tidrangequals != NIL)
	{
		/*
		 * 该路径不使用连接子句，但由于其 tlist 中的 LATERAL 引用，
		 * 可能仍然需要参数化。
		 */
		Relids		fc_required_outer = fc_rel->lateral_relids;

		add_path(fc_rel, (Path *) create_tidrangescan_path(fc_root, fc_rel,
														fc_tidrangequals,
														fc_required_outer));
	}

	/*
	 * 尝试使用从 EquivalenceClasses 中提取的相等子句生成参数化的 TidPaths。
	 * （这很重要，因为简单的 "t1.ctid =
	 * t2.ctid" 子句将变成 EC。）
	 */
	if (fc_rel->has_eclass_joins)
	{
		List	   *fc_clauses;

		/* 生成子句，跳过任何连接到 lateral_referencers 的子句 */
		fc_clauses = generate_implied_equalities_for_column(fc_root,
														 fc_rel,
														 fc_ec_member_matches_ctid,
														 NULL,
														 fc_rel->lateral_referencers);

		/* 为每个可用的连接子句生成路径 */
		fc_BuildParameterizedTidPaths(fc_root, fc_rel, fc_clauses);
	}

	/*
	 * 还考虑使用"松散"连接质疑的参数化 TidPaths。形式为 "t1.ctid = t2.ctid" 的质疑，
	 * 如果它们是外连接质疑，例如，将转换成这些。
	 */
	fc_BuildParameterizedTidPaths(fc_root, fc_rel, fc_rel->joininfo);
}
